/**
/ * @author Goh Zhe Yi & Syed Irfan
 */
package sg.edu.nus.iss.team4ft.manager;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import sg.edu.nus.iss.team4ft.exception.BillingException;
import sg.edu.nus.iss.team4ft.service.Account;
import sg.edu.nus.iss.team4ft.service.Address;
import sg.edu.nus.iss.team4ft.service.Bill;
import sg.edu.nus.iss.team4ft.service.CallService;
import sg.edu.nus.iss.team4ft.service.CallTransfer;
import sg.edu.nus.iss.team4ft.service.Customer;
import sg.edu.nus.iss.team4ft.service.CustomerAccount;
import sg.edu.nus.iss.team4ft.service.DataService;
import sg.edu.nus.iss.team4ft.service.Feature;
import sg.edu.nus.iss.team4ft.service.FeatureCode;
import sg.edu.nus.iss.team4ft.service.Plan;
import sg.edu.nus.iss.team4ft.service.PlanCode;
import sg.edu.nus.iss.team4ft.service.UsageChargable;
import sg.edu.nus.iss.team4ft.service.VoicePlan;
import sg.edu.nus.iss.team4ft.util.AppConstant;
import sg.edu.nus.iss.team4ft.util.BillIOService;
import sg.edu.nus.iss.team4ft.util.DateUtil;
import sg.edu.nus.iss.team4ft.util.IOService;
import sg.edu.nus.iss.team4ft.util.NumberUtil;

public class BillManager {
	private Bill bill;
	
	public BillManager(){
		this.setBill(new Bill());
	}
	
	public Boolean billFileExist(int month,int year){
		String mth = NumberUtil.lPad(month, 2);
		String objFilename = "data\\billsobj_"+year+mth+".txt";		
		
		return IOService.fileExists(objFilename);
	}
	
	/**
	 * Generates monthly bill for all accounts based on the month and year
	 * @param month
	 * @param year
	 * @return
	 * @throws IOException
	 * @throws CloneNotSupportedException
	 * @throws ParseException 
	 * @throws BillingException 
	 * @throws ClassNotFoundException 
	 * @throws NullPointerException 
	 */
	public Map<String,Bill> genMonthlyBill(int month,int year) throws BillingException{
		
		List<CustomerAccount> custAccts = new ArrayList<CustomerAccount>();				
		List<Bill> allBills = new ArrayList<Bill>();
		Map<String,Bill> acctMap = new HashMap<String,Bill>();
		
		try{
			//Get all accounts
			Date date = DateUtil.getMaxDate(DateUtil.parse(month+"-"+year, DateUtil.DATE_UI));
			custAccts = new CustomerAccountManager().getAllCustomerAccount();	
					
			//Get Bill for each customer
			for(CustomerAccount custAcct: custAccts){
				if(custAcct.isActive(date)){
					Bill bill = new Bill(); 
					bill=getBill(custAcct,month,year);
					acctMap.put(custAcct.getAccount().getAccountNumber(), bill);
					allBills.add(bill);
				}
			}
			
			
			//set Payments
			appendPaymentData(allBills,month,year);
			
			String mth = NumberUtil.lPad(month, 2);
			String filename = "data\\bills_"+year+mth+".txt";
			String objFilename = "data\\billsobj_"+year+mth+".txt";
						
			IOService.deleteFile(objFilename);
			IOService.saveObjToFile(acctMap, objFilename);
			
			IOService.deleteFile(filename);
			for(Bill bill: allBills){
				StringBuffer fullBillReport = new StringBuffer();
				fullBillReport.append(getBillPg1Str(bill) +"\n\n\n"+ getBillPg2Str(bill));
				fullBillReport.append("\n\n");
				fullBillReport.append("========End of Report=========================================================");
				fullBillReport.append("\n\n");
				BillIOService.createBillReportFile(fullBillReport.toString(), filename);
			}				
		}catch(Exception e){
			throw new BillingException(e);
		}
		return acctMap;
	}	

	public Bill getBill(CustomerAccount custAcct, int month, int year) throws ParseException, CloneNotSupportedException, NullPointerException, IOException{
		Bill bill = new Bill();
		Account acc = custAcct.getAccount();
		Customer cust = custAcct.getCustomer();
		
		Date date = DateUtil.getMaxDate(DateUtil.parse(month+"-"+year, DateUtil.DATE_UI));

		bill.setAccount(acc);
		bill.setCustomer(cust);
		setCoDetails(bill);
		bill.setBillDate(date);
		
		Double currCharge = getCurrCharge(acc, date);
		bill.setCurrCharges(currCharge);
		bill.setTotalCharge(bill.getTotalCurrAndGST()+bill.getPrevBalance());
						
		return bill;
	}

	@SuppressWarnings("unchecked")
	public Bill getBill(String acctNum, int month, int year) throws BillingException, IOException, ClassNotFoundException, ParseException, CloneNotSupportedException{
		
		String objFilename = "";
		Map<String,Bill> acctMap = null;
		
		Bill myBill = new Bill();

		String mth = NumberUtil.lPad(month, 2);
		objFilename = "data\\billsobj_" + year + mth + ".txt";
		if (IOService.fileExists(objFilename)) {
			acctMap = (Map<String, Bill>) IOService.readObjFromFile(objFilename);
			Bill bill = acctMap.get(acctNum);
			myBill = bill;
			if (bill == null) {
				throw new BillingException("No Bill found for Account: "+acctNum);
			}
		} else {
			throw new BillingException("Billing File: "+objFilename+ " not found");
		}
		
		return myBill;	
	} 

	/**
	 * Creates the 1st page of the bill
	 * @param accNum
	 * @param date
	 * @return String of the 1st page of the bill
	 * @throws BillingException 
	 * @throws CloneNotSupportedException 
	 */
	public String getBillPg1Str(Bill bill) throws BillingException, CloneNotSupportedException{
		if(bill == null)
			throw new BillingException();
		
		this.setBill(bill);
		StringBuffer custBill = new StringBuffer("\n");

		//1st line (Company's Name)
		custBill.append(bill.getCoNameShort());
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append(bill.getCoName());
		custBill.append("\n");
		
		//Company Address
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append(bill.getCoAdd().getAddressLine1());
		custBill.append("\n");
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append(bill.getCoAdd().getAddressLine2());
		custBill.append("\n");
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append(bill.getCoAdd().getCountry()+" "+bill.getCoAdd().getPostalCode());
		custBill.append("\n");
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append("Call "+AppConstant.CO_TEL);
		custBill.append("\n");
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append("Email: "+AppConstant.CO_EMAIL);
		custBill.append("\n");
		
		//Customer Information
		custBill.append(bill.getCustomer().getName());
		custBill.append("\n");
		custBill.append(bill.getCustomer().getBillingAddress().getAddressLine1());
		custBill.append("\n");
		custBill.append(bill.getCustomer().getBillingAddress().getAddressLine2());
		custBill.append("\n");
		custBill.append(bill.getCustomer().getBillingAddress().getPostalCode());
		custBill.append("\n");
		custBill.append("\n");
		//Account No.
		custBill.append(AppConstant.BILL_ACC_CTR_INDENT);
		custBill.append("Account No: "+bill.getAccount().getAccountNumber());
		custBill.append("\n");
		//Bill Date
		custBill.append(AppConstant.BILL_ACC_CTR_INDENT);
		custBill.append("Bill date: "+bill.getBillDateStr());
		custBill.append("\n");
		//Due Date
		custBill.append(AppConstant.BILL_ACC_CTR_INDENT);
		custBill.append("Due date: "+bill.getDueDateStr());
		custBill.append("\n");
		custBill.append("\n");
		
		//Bill Summary
		custBill.append(AppConstant.SUMM_SUBTITLE);
		custBill.append("\n");
		custBill.append("\n");
		custBill.append(AppConstant.SUMM_PREV_BAL+bill.getPrevBalanceStr());
		custBill.append("\n");
		custBill.append(AppConstant.SUMM_PAYMENT+bill.getPaymtsStr());
		custBill.append("\n");
		custBill.append(String.format(AppConstant.SUMM_CURR_CHRG,bill.getDueDateStr())+bill.getTotalCurrAndGSTStr());
		custBill.append("\n");
		custBill.append(AppConstant.SUMM_PLS_PAY+bill.getTotalChargeStr());
		custBill.append("\n");
		custBill.append("\n");
		custBill.append("\n");
		custBill.append("\n");
		custBill.append("\n");
		custBill.append("\n");
		custBill.append(AppConstant.BILL_PAGE_NO_INDENT);
		custBill.append(AppConstant.BILL_PG1);
		
				
		return custBill.toString();	
	}
	
	/**
	 * Creates the 2nd page of the bill
	 * @param accNum
	 * @param date
	 * @return String of the 2nd page of the bill
	 * @throws CloneNotSupportedException 
	 * @throws BillingException 
	 * @throws ParseException 
	 * @throws IOException 
	 * @throws NullPointerException 
	 */
	public String getBillPg2Str(Bill bill) throws CloneNotSupportedException, BillingException, NullPointerException, IOException, ParseException{
		if(bill == null)
			throw new BillingException();
		else
			this.setBill(bill);
		
		StringBuffer custBill = new StringBuffer("\n");	
		Date maxDate = DateUtil.getMaxDate(bill.getBillDate());
		
		//1st line Account Info
		custBill.append(bill.getCoNameShort());
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append("Account No: "+bill.getAccount().getAccountNumber());
		custBill.append("\n");
		custBill.append(AppConstant.BILL_CO_ADD_INDENT);
		custBill.append("Bill date: "+bill.getBillDateStr());
		custBill.append("\n");
		custBill.append("\n");
		
		//3rd line Headings
		custBill.append(AppConstant.BILL_AMT_HEADER_INDENT);
		custBill.append(AppConstant.DTL_AMT+"\t\t"+AppConstant.DTL_TTL);
		custBill.append("\n");
		
		/*Bill Details*/
		//Summary Payment
		if(bill.getPaymts()!=0){
			custBill.append(AppConstant.PG2_SUMM_PAYMT_DTL);			
			custBill.append("\n");
			custBill.append(AppConstant.PG2_PAYMT_RCV+"\t\t"+bill.getPayDateStr()+"\t\t\t\t"+bill.getPaymtsStr()+"\t\t"+bill.getPaymtsStr());
			custBill.append("\n");
			custBill.append("\n");
		}
		
		//Summary Current Charges
		
		custBill.append(AppConstant.PG2_SUMM_CURR_CHRG);
		custBill.append("\n");
		custBill.append("\n");
		
		
		//Summary of the customer's plans 
		Double totalCurrentCharge=0.0;
		
		for(Plan plan:bill.getAccount().getPlans()){
			Double totalPlanCharge=0.0;
			if(plan instanceof UsageChargable){
				if(plan instanceof VoicePlan){
					custBill.append(plan.getServiceName());
					custBill.append("\n");
					custBill.append(AppConstant.PG2_ACC_SUB_CHRG+AppConstant.BILL_AMT_INDENT+NumberUtil.format(plan.getSubscriptionCharge(maxDate),2));
					
					custBill.append("\n");
					
					custBill.append(AppConstant.PG2_USG_CHRG+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((VoicePlan)plan).calculateMonthlyUsage(maxDate),2));
					
					custBill.append("\n");
					
					
					custBill.append("\n");
					totalPlanCharge=totalPlanCharge+plan.getSubscriptionCharge(maxDate)+((VoicePlan)plan).calculateMonthlyUsage(maxDate);
										
					
				}
				else{
					
					custBill.append(plan.getServiceName());
					custBill.append("\n");
					custBill.append(AppConstant.PG2_ACC_SUB_CHRG+AppConstant.BILL_AMT_INDENT+NumberUtil.format(plan.getSubscriptionCharge(maxDate),2));
					custBill.append("\n");
					custBill.append(AppConstant.CHANNEL_CHARGE+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((UsageChargable)plan).calculateMonthlyUsage(maxDate),2));
					custBill.append("\n");
					totalPlanCharge=totalPlanCharge+plan.getSubscriptionCharge(maxDate)+((UsageChargable)plan).calculateMonthlyUsage(maxDate);
				}
			
				custBill.append(AppConstant.BILL_TTL_INDENT+NumberUtil.format(totalPlanCharge,2));
				custBill.append("\n");
				totalCurrentCharge+=totalPlanCharge;
			}
		}
		
		custBill.append("\n");
		custBill.append("\n");
		custBill.append(AppConstant.PG2_TTL_GST);
		custBill.append(AppConstant.BILL_TTL_INDENT+NumberUtil.format(totalCurrentCharge*AppConstant.GST,2));
		custBill.append("\n");
		
		custBill.append(AppConstant.PG2_TTL_CURR_CHRG+"\t\t\t\t\t\t\t\t"+bill.getTotalCurrAndGSTStr());
		custBill.append("\n");
		
		
		
		//ACCOUNT DETAILS of the customer				
		custBill.append(AppConstant.PG2_ACC_DETAILS);
		custBill.append("\n");
		custBill.append("\t\t\t\t\t\t\t" + AppConstant.AMOUNT+"\t\t"+AppConstant.TOTAL);
		custBill.append("\n");
		
		Double totalCurrentCharges=0.0;
		for(Plan plan:bill.getAccount().getPlans()){
			Double totalCharge=0.0;
			if(plan instanceof UsageChargable){
				if(plan instanceof VoicePlan){
					custBill.append(plan.getServiceName() +' '+ AppConstant.Number+' '+((VoicePlan)plan).getTelephoneNumber());
					custBill.append("\n");
					custBill.append(AppConstant.PG2_ACC_SUB_CHRG);
					custBill.append("\n");
					if(plan.getPlanCode()==PlanCode.DigitalVoice){
						AppConstant.LABEL_TEMP="Line";
					}
					else{
						AppConstant.LABEL_TEMP="Mobile";
					}
						
	              Double totalFeatureSubsriptionCharge=0.0;
					for(Feature feature: plan.getFeatures()){
						Double featureSubscriptionCharge=0.0;
						
					if(feature.isSubscriptionActive(bill.getBillDate())){	
						
						if(feature instanceof CallService && feature.getFeatureCode()==FeatureCode.LocalCall){
					     custBill.append(AppConstant.LABEL_TEMP+AppConstant.BILL_AMT_INDENT+"\t"+NumberUtil.format(((CallService)feature).getSubscriptionCharge(maxDate),2));
					     custBill.append("\n");
					      featureSubscriptionCharge =((CallService)feature).getSubscriptionCharge(maxDate);
						}			
						if(feature instanceof CallTransfer && feature.getFeatureCode()==FeatureCode.CallTransfer){
						     custBill.append(AppConstant.FTR_CALL_TRANSFER+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((CallTransfer)feature).getSubscriptionCharge(maxDate),2));
						     custBill.append("\n");
						      featureSubscriptionCharge =((CallTransfer)feature).getSubscriptionCharge(maxDate);
							}
						if(feature instanceof CallService && feature.getFeatureCode()==FeatureCode.RoamingCall){
						     custBill.append(AppConstant.FTR_ROAMING+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((CallService)feature).getSubscriptionCharge(maxDate),2));
						     custBill.append("\n");
						      featureSubscriptionCharge =((CallService)feature).getSubscriptionCharge(maxDate);
							}
						if(feature instanceof DataService && feature.getFeatureCode()==FeatureCode.DataService){
						     custBill.append(AppConstant.FTR_DS+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((DataService)feature).getSubscriptionCharge(maxDate),2));
						     custBill.append("\n");
						      featureSubscriptionCharge =((DataService)feature).getSubscriptionCharge(maxDate);
						     
							}
					}
					totalFeatureSubsriptionCharge=totalFeatureSubsriptionCharge+featureSubscriptionCharge;
					
						
						
					}
					custBill.append("\n");
					custBill.append(AppConstant.PG2_USG_CHRG);
					custBill.append("\n");
					 
					Double totalFeatureUsageCharge=0.0;
					for(Feature feature: plan.getFeatures()){
						Double featureUsageCharge=0.0;
						if(feature.isSubscriptionActive(bill.getBillDate())){	
							if(feature instanceof CallService && feature.getFeatureCode()==FeatureCode.LocalCall){
							     custBill.append(AppConstant.FTR_LOCAL_CALLS+AppConstant.BILL_AMT_INDENT+"\t"+NumberUtil.format(((CallService)feature).calculateMonthlyUsage(maxDate),2));
							     custBill.append("\n");
							      featureUsageCharge=((CallService)feature).calculateMonthlyUsage(maxDate);
								}
								
							if(feature instanceof CallService && feature.getFeatureCode()==FeatureCode.InternationalCall){
								     custBill.append(AppConstant.FTR_IDD+AppConstant.BILL_AMT_INDENT+"\t"+NumberUtil.format(((CallService)feature).calculateMonthlyUsage(maxDate),2));
								     custBill.append("\n");
								      featureUsageCharge=((CallService)feature).calculateMonthlyUsage(maxDate);
									}
							if(feature instanceof CallService && feature.getFeatureCode()==FeatureCode.RoamingCall){
								     custBill.append(AppConstant.FTR_ROAMING+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((CallService)feature).calculateMonthlyUsage(maxDate),2));
								     custBill.append("\n");
								      featureUsageCharge=((CallService)feature).calculateMonthlyUsage(maxDate);
									}
						}
						totalFeatureUsageCharge=totalFeatureUsageCharge + featureUsageCharge;				
					}
					totalCharge=totalFeatureSubsriptionCharge+totalFeatureUsageCharge;				
				}
				else{
					custBill.append(plan.getServiceName());
					custBill.append("\n");
					custBill.append(AppConstant.PG2_ACC_SUB_CHRG+AppConstant.BILL_AMT_INDENT+NumberUtil.format(plan.getSubscriptionCharge(maxDate),2));
					custBill.append("\n");
					custBill.append(AppConstant.CHANNEL_CHARGE+AppConstant.BILL_AMT_INDENT+NumberUtil.format(((UsageChargable)plan).calculateMonthlyUsage(maxDate),2));
					custBill.append("\n");
					totalCharge=plan.getSubscriptionCharge(maxDate)+((UsageChargable)plan).calculateMonthlyUsage(maxDate);
				}
				custBill.append(AppConstant.BILL_TTL_INDENT+NumberUtil.format(totalCharge, 2));
				custBill.append("\n");
				custBill.append("\n");
				totalCurrentCharges+=totalCharge;			
			}
		}
		
		custBill.append("\n");
		custBill.append(AppConstant.PG2_TTL_GST);
		custBill.append(AppConstant.BILL_TTL_INDENT+NumberUtil.format(totalCurrentCharges*AppConstant.GST,2));
		custBill.append("\n");
		
		custBill.append(AppConstant.PG2_TTL_CURR_CHRGS+"\t\t\t\t\t\t\t\t"+bill.getTotalCurrAndGSTStr());
		custBill.append("\n");
		
		
		custBill.append("\n");
		custBill.append(AppConstant.BILL_PAGE_NO_INDENT);
		custBill.append(AppConstant.BILL_PG2);		
		return custBill.toString();	
	}
	
	 
	/**
	 * Gets the current charge for the customer at the specific period
	 * @param acc
	 * @param date
	 * @return
	 * @throws CloneNotSupportedException
	 * @throws NullPointerException
	 * @throws IOException
	 * @throws ParseException
	 */
	private Double getCurrCharge(Account acc,Date date) throws CloneNotSupportedException, NullPointerException, IOException, ParseException {
		Double currCharge = 0.00;
		date=DateUtil.getMaxDate(date);
		for(Plan plan: acc.getPlans()){
			Double totalPlanCharge=0.00;
			if(plan instanceof UsageChargable){
				if(plan instanceof VoicePlan){				
					totalPlanCharge=totalPlanCharge+plan.getSubscriptionCharge(date)+((VoicePlan)plan).calculateMonthlyUsage(date);	
				}
				else{
					totalPlanCharge=totalPlanCharge+plan.getSubscriptionCharge(date)+(((UsageChargable)plan).calculateMonthlyUsage(date));
				}
				currCharge+=totalPlanCharge;
			}
		}
		return currCharge;
	}
	
	/**
	 * append total charge to payment data for prev balance.
	 * @param allBills
	 * @param year 
	 * @param month 
	 * @throws IOException
	 */
	@SuppressWarnings({ "unchecked" })
	private void appendPaymentData(List<Bill> allBills, int month, int year) {
		//if month is Jan, previous month would be dec of prev yr
		if(month==1){
			month=12;
			year=year-1;
		}else
			month=month-1;
		
		String objFilename = "";
		Map<String,Bill> acctMap = null;
		try {
			String mth = NumberUtil.lPad(month, 2);	
			objFilename = "data\\billsobj_"+year+mth+".txt";
				if (IOService.fileExists(objFilename)) {
					acctMap = (Map<String, Bill>) IOService.readObjFromFile(objFilename);
				}
		} catch (Exception e) {	
			e.printStackTrace();
		}

		for (Bill bill : allBills) {
			// any error or empty value; set payments to 0
			bill.setPaymts(0.00);
			bill.setPrevBalance(0.00);
			if (acctMap != null) {
				Bill prevBill = acctMap.get(bill.getAccount().getAccountNumber());
				if (prevBill != null) {
					try {
						Calendar date = Calendar.getInstance();

						date.set(Calendar.MONTH, month);
						date.set(Calendar.YEAR, year);
						Date payDate = date.getTime();
						payDate = DateUtil.getMaxDate(payDate);

						bill.setPayDate(payDate);
						bill.setPaymts(0.0-prevBill.getTotalCharge());
						bill.setPrevBalance(prevBill.getTotalCharge());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * Gets all the company details and place them in bill object
	 * @param bill
	 */
	private void setCoDetails(Bill bill) {
		bill.setCoName(AppConstant.CO_NAME);
		bill.setCoNameShort(AppConstant.CO_NAME_SHORT);
		bill.setCoAdd(this.getCoAdd());
	}
	
	/**
	 * Gets company address from AppConstant
	 * @return
	 */
	private Address getCoAdd() {		
		return new Address(AppConstant.CO_ADD_LINE_1, AppConstant.CO_ADD_LINE_2, null, AppConstant.CO_CTRY, Integer.valueOf(AppConstant.CO_PSTL_CD));
	}

	public Bill getBill() {
		return bill;
	}

	public void setBill(Bill bill) {
		this.bill = bill;
	}
		
	}

